前一天我們完成了第二條管理者主線的「教室細項設定」後,接下來我們會進入到第三條管理者功能-「所有教室狀態」。
今天我們會說明這條功能線,但我們會先著重在 AllClassroomsActivity.kt,也就是所有教室狀態的總覽頁。而和前一條主線一樣,這頁面也會有 Adapter 以及接收 CLASSROOM_NAME的檔案來做職責分離,這會在我們後面幾天的文章說明。
管理者在修改前,其實非常需要一個能「瀏覽所有教室狀態」的總覽入口,這是我最初設計中沒有想到的部分,後來跟組內討論後,才發現確實需要。
進入到此畫面後,我們可以看到所有教室的教室類型及是否可飲食的資訊;右下角的「+」按鈕,也可以提供管理方便去新增教室,這樣既能確定教室有正式新增在目前 App 上,也能避免疏忽。
| UI 畫面 | 操作流程 |
|---|---|
![]() |
![]() |
AllClassroomsActivity.kt 負責什麼和 Hermes Agent 一起重讀 AllClassroomsActivity.kt 後, 它是管理者「所有教室狀態」的頁面。負責顯示所有教室清單、顯示每間教室的名稱/類型/是否可飲食、讓管理者點選某間教室看更完整課表狀態,並提供新增教室功能。
剖析程式碼後,重點可以分為以下三大核心機制:
viewModel.allSchedules.observe(this, Observer { schedules ->
val classroomInfos = schedules.map { schedule ->
ClassroomInfo(
schedule.classroom,
schedule.classroomType ?: "未知",
schedule.classroomType == "Normal"
)
}.sortedBy { it.name }
adapter.updateData(classroomInfos)
})
ClassroomSchedule 轉成 UI 顯示用的 ClassroomInfo,只保留畫面需要的教室名稱、類型、是否可飲食。// 1. 正規表達式(Regex)格式驗證
val name = classroomNameInput.text.toString().trim().uppercase()
if (name.isEmpty() || !name.matches(Regex("^(SF|ES)\\d+$"))) {
Toast.makeText(this, "教室名稱格式不符 ...", Toast.LENGTH_LONG).show()
return@setOnClickListener
// 2. 非同步查重機制
lifecycleScope.launch {
val exists = viewModel.checkExists(name)
if (exists) {
Toast.makeText(this@AllClassroomsActivity, "提醒:已經有這間教室了,不進行新增", Toast.LENGTH_LONG).show()
} else {
viewModel.insert(name, type)
Toast.makeText(this@AllClassroomsActivity, "教室 $name 新增成功", Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
}
SF 或 ES 開頭,後面接數字,例如 SF304、ES201。新增前會先檢查教室是否已存在;存在就不新增,不存在才呼叫 insert()。fun insert(classroomName: String, classroomType: String) = viewModelScope.launch {
val newSchedule = ClassroomSchedule(
classroom = classroomName,
classroomType = classroomType,
mon1 = null, mon2 = null, ...,
fri8 = null
)
repository.insert(newSchedule)
}
ClassroomSchedule。所有時段都是 null,代表初始狀態沒有課;查詢頁用 != "X" 判斷可用,所以這些時段會被視為空閒。ManagerActivity
「點擊所有教室狀態」
↓
AllClassroomsActivity
↓
Repository.getAllSchedules()
「取得所有教室課表」
↓
ClassroomSchedule
「取得所有教室資料」
↓
轉換成 ClassroomInfo
「整理列表需要的資料」
↓
AllClassroomsAdapter
「顯示所有教室總覽」
AllClassroomsActivity
「點擊新增教室」
↓
輸入教室資料
↓
檢查教室名稱格式
↓
檢查教室是否已存在
↓
檢查通過?
┌────┴────┐
│ │
是 否
│ │
▼ ▼
建立 顯示錯誤提示
ClassroomSchedule
│
▼
Repository / DAO
「新增教室資料」
│
▼
Room Database
「儲存新教室」
AllClassroomsActivity 是管理者瀏覽所有教室狀態的頁面。它從 ManagerActivity 的「所有教室狀態」入口進入,使用 all_classrooms.xml,畫面包含所有教室列表、離開按鈕與新增教室浮動按鈕。
AllClassroomsViewModel 從 Repository 取得所有 ClassroomSchedule,並在 observe 時轉成 ClassroomInfo:名稱取自 classroom,類型取自 classroomType,是否可飲食則用 classroomType == "Normal" 推導。SF 或 ES 開頭加數字,並且不能已經存在。新增成功後會 insert 一筆新的 ClassroomSchedule,所有課表時段初始都是 null,代表沒有課,查詢時會被視為可用。REPLACE 策略,一旦手動新增的教室名稱(Primary Key)與 CSV 原始資料撞名,系統會在沒有任何警示或確認的情況下,直接用 CSV 的初始資料,強制覆蓋掉手動修改的內容,且完全沒有任何警告機制。AllClassroomsActivity.kt 裡同時有 Activity、ViewModel、Factory,小專案可行,但如果未來需要再發展可能需要拆分。讀到 AllClassroomsActivity 時,我看到了管理者功能裡的第三條主線:瀏覽所有教室狀態。這頁不是只列出教室名稱,而是把每間教室轉成 ClassroomInfo,顯示名稱、教室類型與是否可飲食。這讓管理者可以從總覽頁快速知道目前資料庫裡有哪些教室。
這頁也提供新增教室功能。新增時會檢查名稱格式,必須是 SF 或 ES 開頭並接數字,也會檢查是否已經存在。新增成功後,程式會建立一筆新的 ClassroomSchedule,並把所有時段設為 null。這代表新教室預設所有時段都沒有課,會被查詢頁視為可用。
這裡也延續了前面看到的技術債:大樓代碼仍然硬編碼在程式裡,而且新增資料和 CSV 預載資料之間的關係需要小心。若資料庫重建或 migration 策略改變,管理者新增的資料可能會有遺失風險。
一句話重點總結:
AllClassroomsActivity顯示所有教室的狀態總覽,並提供新增教室功能;新增教室會建立一筆所有時段為null的ClassroomSchedule。
掌握了「所有教室狀態」的頁面架構與新增防呆機制後,畫面上那一張張資訊卡片,究竟是如何被優雅地繪製與組裝的?
下一篇,我們將焦點對準 AllClassroomsAdapter.kt:拆解 Adapter 如何將精簡後的 ClassroomInfo 綁定至視圖,卡片會顯示名稱、類型、是否可飲食;並把單純的卡片點擊化為導航入口,帶領使用者一鍵跳轉至「完整課表頁」。